import React, {Component} from 'react'
import {MarkdownPreview} from 'react-marked-markdown';
// import py1_1 from '../static/py1_1.md'
// import marked from 'marked';

class Py25 extends Component {
    constructor(props) {
        super(props);
        this.state = {
           value: `

从 Google 的无人驾驶汽车到可以识别假钞的自动售卖机，机器视觉一直都是一个应用广
泛且具有深远的影响和雄伟的愿景的领域。在这一章里，我们将重点介绍机器视觉的一个
分支：文字识别，介绍如何用一些 Python 库来识别和使用在线图片中的文字。

当你不想让自己的文字被网络机器人采集时，把文字做成图片放在网页上是常用的办法。
在一些联系人通讯录里经常可以看到，一个邮箱地址被部分或全部转换成图片。人们可能
觉察不出明显的差异，但是机器人阅读这些图片会非常困难，这种方法可以防止多数垃圾
邮件发送器轻易地获取你的邮箱地址。

利用这种人类用户可以正常读取但是大多数机器人都没法读取的图片，验证码
（CAPTCHA）就出现了。验证码读取的难易程度也大不相同，有些验证码比其他的更加难
读，后面我们会介绍这种问题。

但是，验证码并不是网络爬虫数据采集时需要进行图像转文字翻译工作的唯一对象。目
前，有很多文档都是简单地扫描后直接放到网上，它们和互联网上的很多文档一样都没法
儿直接使用，尽管它们都“近在眼前”。如果无法将图像转为文字，要想使用这些文档的
内容，就只能人工手敲了——没人愿意花时间干这事儿。

将图像翻译成文字一般被称为光学文字识别（Optical Character Recognition，OCR）。可以
实现 OCR 的底层库并不多，目前很多库都是使用共同的几个底层 OCR 库，或者是在上面
进行定制。这类 OCR 系统有时会变得非常复杂，所有我建议你在实践这一章的代码示例
之前先阅读下一节的内容。

> 11.1　OCR库概述

在读取和处理图像、图像相关的机器学习以及创建图像等任务中，Python 一直都是非常出
色的语言。虽然有很多库可以进行图像处理，但在这里我们只重点介绍两个库：Pillow 和
Tesseract。

每个库都可以从它们的网站上下载并安装（http://pillow.readthedocs.org/installation.html 和https://pypi.python.org/pypi/pytesseract），或者用第三方管理器（像 pip）通过“pillow”和
“pytesseract”进行安装。

> 11.1.1　Pillow

尽管 Pillow 算不上是图像处理功能最全的库，但是它拥有你需要使用的全部功能，除非你
要用 Python 重写一个 Photoshop 或进行更加复杂的研究。它也是一个文档健全且十分易用
的库。

Pillow 是从 Python 2.x 版本的 Python 图像库（Python Imaging Library，PIL）分出来的，支
持 Python 3.x 版本。和 PIL 一样，Pillow 也可以轻松地导入代码，并通过大量的过滤、修
饰甚至像素级的变换操作处理图片：
   
    from PIL import Image, ImageFilter

    kitten = Image.open("kitten.jpg")
    blurryKitten = kitten.filter(ImageFilter.GaussianBlur)
    blurryKitten.save("kitten_blurred.jpg")
    blurryKitten.show()
   
在上面这个例子中，图片 kitten.jpg 会在默认的图片浏览器里打开，不过看着会有点儿模
糊。之后这个比较模糊的图片被另存为 kitten_blurred.jpg，与原图放在一个文件夹里。
我们可以用 Pillow 完成图片的预处理，让机器可以更方便地读取图片。除了这些简单的
事情之外，Pillow 还可以完成许多复杂的图像处理工作。更多的信息，请查看 Pillow 文档
（http://pillow.readthedocs.org）。

> 11.1.2　Tesseract

Tesseract 是一个 OCR 库，目前由 Google 赞助（Google 也是一家以 OCR 和机器学习技术
闻名于世的公司）。Tesseract 是目前公认最优秀、最精确的开源 OCR 系统。

除了极高的精确度，Tesseract 也具有很高的灵活性。它可以通过训练识别出任何字体（只
要这些字体的风格保持不变就可以，后面我们会介绍），也可以识别出任何 Unicode 字符。

和本书前面提到的那些库不同，Tesseract 是一个 Python 的命令行工具，不是通过 import
语句导入的库。安装之后，要用 tesseract 命令在 Python 的外面运行。

安装Tesseract
在 Windows 系统上，下载方便的可执行安装文件（https://code.google.com/p/tesseract-ocr/downloads/list）安装即可。写到这里的时候，最新的版本是 3.02，新版本应该也可以这样
安装。

Linux 用户可以通过 apt-get 安装：
 
    $sudo apt-get tesseract-ocr
  
在 Mac 上安装 Tesseract 有点儿复杂，不过用 Homebrew（http://brew.sh/）等第三方库可以
很方便地安装。Homebrew 在第 5 章介绍 MySQL 安装过程时提到过。例如，你可以用下
面两行代码首先安装 Homebrew，然后再安装 Tesseract：
   
    $ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/ \install/master/install)"
    $brew install tesseract
   
也可以从 Tesseract 项目下载页面（https://code.google.com/p/tesseract-ocr/downloads/list）下
载源代码安装。

要使用 Tesseract 的功能，比如后面的示例中训练程序识别字母，你需要先在系统中设置一
个新的环境变量 $TESSDATA_PREFIX ，让 Tesseract 知道训练的数据文件存储在哪里。

在大多数 Linux 系统和 Mac OS X 系统上，你可以这么设置：
    
    $export TESSDATA_PREFIX=/usr/local/share/
    
值得注意的是，虽然 /usr/local/share/ 是 Tesseract 的默认数据存储位置，但是你还是应
该仔细地检查一下，确保自己的安装没问题。

在 Windows 系统上也类似，你可以通过下面这行命令设置环境变量：
  
    #setx TESSDATA_PREFIX C:\Program Files\Tesseract OCR\
    

> 11.1.3　NumPy

虽然 NumPy 并非解决 OCR 问题时必须使用的库，但是如果你想训练 Tesseract 识别本章后
面提到的字符或字体，那么就会用到它。NumPy 是一个非常强大的库，具有大量线性代数
以及大规模科学计算的方法。因为 NumPy 可以用数学方法把图片表示成巨大的像素数组，
所以它可以流畅地配合 Tesseract 完成任务。

和其他 Python 库一样，NumPy 可以通过第三方包管理器（比如 pip）来安装：
  
    $pip install numpy
    

> 11.2　处理格式规范的文字

你要处理的大多数文字都是比较干净、格式规范的。格式规范的文字通常可以满足一些需
求，不过究竟什么是“格式混乱”，什么算“格式规范”，确实因人而异。

通常，格式规范的文字具有以下特点：

* 使用一个标准字体（不包含手写体、草书，或者十分“花哨的”字体）
* 虽然被复印或拍照，字体还是很清晰，没有多余的痕迹或污点
* 排列整齐，没有歪歪斜斜的字
* 没有超出图片范围，也没有残缺不全，或紧紧贴在图片的边缘

文字的一些格式问题在图片预处理时可以进行解决。例如，可以把图片转换成灰度图，调
整亮度和对比度，还可以根据需要进行裁剪和旋转。但是，这些做法在进行更具扩展性的
训练时会遇到一些限制。

你可以通过下面的命令运行 Tesseract，读取文件并把结果写到一个文本文件中：
    
    $tesseract text.tif textoutput | cat textoutput.txt
  
输出结果的第一行是 Tesseract 的版本信息，表明它正在运行，后面是图片识别结果
textoutput.txt 文件里的内容：
    
    Tesseract Open Source OCR Engine v3.02.02 with Leptonica
    This is some text, written in Arial, that will be read by
    Tesseract. Here are some symbols: !@#$%"&'()
    
你会发现识别结果很准确，不过符号“^”和“ * ”分别被表示成了双引号和单引号。大体
上可以让你很舒服地阅读。

图片先进行模糊处理，转换成一个 JPG 压缩格式的图片，再增加一点儿背景渐变，识别效
果就会变得很差

Tesseract 不能完整处理这个图片，主要是因为图片背景色是渐变的，最终结果是这样：
    
    This is some text, written In Arlal, that"
    Tesseract. Here are some symbols: _
  
你会发现，随着背景色从左到右不断加深，文字变得越来越难以识别，Tesseract 识别出的
每一行的最后几个字符都是错的。另外，经过 JPG 格式转换和模糊效果处理，Tesseract 更
难识别小写“i”和大写“I”以及数字“1”。

遇到这类问题，可以先用 Python 脚本对图片进行清理。利用 Pillow 库，我们可以创建一个
阈值过滤器来去掉渐变的背景色，只把文字留下来，从而让图片更加清晰，便于 Tesseract
读取：
   
    from PIL import Image
    import subprocess

    def cleanFile(filePath, newFilePath):
        image = Image.open(filePath)

        # 对图片进行阈值过滤，然后保存
        image = image.point(lambda x: 0 if x<143 else 255)
        image.save(newFilePath)

        # 调用系统的tesseract命令对图片进行OCR识别
        subprocess.call(["tesseract", newFilePath, "output"])

        # 打开文件读取结果
        outputFile = open("output.txt", 'r')
        print(outputFile.read())
        outputFile.close()

    cleanFile("text_2.jpg", "text_2_clean.png")
    
自动创建的 text_2_clean.png

除了一些标点符号不太清晰或丢失了，大部分文字都被读出来了。Tesseract 给出了最好的
结果：
   
    This us some text' written In Anal, that will be read by
    Tesseract Here are some symbols: !@#$%"&'()
   
图片上的点和逗号经过处理后变得非常小，不论从我们的视角还是 Tesseract 的视角看，这
些都从图片上基本消失了。还有一点失误是把“Arial”看成了“Anal”，这是 Tesseract 把
“r”和“i”都解释成了“n”的结果。

不过，相比上一版本被截断的识别结果，这版算有很大进步了。

Tesseract 最大的缺点是对渐变背景色的处理。之前那个版本中，Tesseract 的算法在读取文
字之前自动尝试调整图片对比度，但是如果你用 Pillow 库这样的工具对图片进行预处理，
效果会更好。

在提交给 Tesseract 处理之前，那些带标题的、带有大片空白的图片，或者有其他问题的图
片，都应该做预处理。

#### 从网站图片中抓取文字

用 Tesseract 读取硬盘里图片上的文字，可能不怎么令人兴奋，但当我们把它和网络爬虫
组合使用时，就能成为一个强大的工具。网站上的图片可能并不是故意把文字做得很花哨
（就像餐馆菜单的 JPG 图片上的艺术字），但它们上面的文字对网络爬虫来说就是隐藏起来
了，我将在下一个例子里演示。

虽然亚马逊的 robots.txt 文件允许抓取网站的产品页面，但是图书的预览页通常不让网络机
器人采集。图书的预览页是通过用户触发 Ajax 脚本进行加载的，预览图片隐藏在 div 节点
下面；其实，普通的访问者会觉得它们看起来更像是一个 Flash 动画，而不是一个图片文
件。当然，即使我们能获得图片，要把它们读成文字也没那么简单。

下面的程序就解决了这个问题：首先导航到托尔斯泰的《战争与和平》的大字号印刷版 ，
打开阅读器，收集图片的 URL 链接，然后下载图片，识别图片，最后打印每个图片的文
字。因为这个程序很复杂，利用了前面几章的多个程序片段，所以我增加了一些注释以让
每段代码的目的更加清晰：
     
    import time
    from urllib.request import urlretrieve
    import subprocess
    from selenium import webdriver

    # 创建新的Selenium driver
    driver = webdriver.PhantomJS(executable_path='<Path to Phantom JS>')
    # 有时我发现PhantomJS查找元素有问题,但是Firefox没有。
    # 如果你运行程序的时候出现问题，去掉下面这行注释，
    # 用Selenium试试Firefox浏览器：
    # driver = webdriver.Firefox()

    driver.get("http://www.amazon.com/War-Peace-Leo-Nikolayevich-Tolstoy/dp/1427030200")
    time.sleep(2)

    # 单击图书预览按钮
    driver.find_element_by_id("sitbLogoImg").click()
    imageList = set()

    # 等待页面加载完成
    time.sleep(5)
    # 当向右箭头可以点击时，开始翻页
    while "pointer" in driver.find_element_by_id("sitbReaderRightPageTurner").get_attribute("style"):
        driver.find_element_by_id("sitbReaderRightPageTurner").click()
        time.sleep(2)
        # 获取已加载的新页面（一次可以加载多个页面，但是重复的页面不能加载到集合中）
        pages = driver.find_elements_by_xpath("//div[@class='pageImage']/div/img")
        for page in pages:
            image = page.get_attribute("src")
            imageList.add(image)
    driver.quit()

    # 用Tesseract处理我们收集的图片URL链接
    for image in sorted(imageList):
        urlretrieve(image, "page.jpg")
        p = subprocess.Popen(["tesseract", "page.jpg", "page"],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
        p.wait()
        f = open("page.txt", "r")
        print(f.read())
     
和我们前面使用 Tesseract 读取的效果一样，这个程序也会完美地打印书中很多长长的段
落，第六页的预览如下所示：
    
    6
    "A word of friendly advice, mon
    cher. Be off as soon as you can,
    that's all I have to tell you. Happy
    he who has ears to hear. Good-by,
    my dear fellow. Oh, by the by!" he
    shouted through the doorway after
    Pierre, "is it true that the countess
    has fallen into the clutches of the
    holy fathers of the Society of je-
    sus?"
    Pierre did not answer and left Ros-
    topchin's room more sullen and an-
    gry than he had ever before shown
    himself.
    
但是，当文字出现在彩色封面上时，结果就不那么完美了：
    
    WEI' nrrd Peace
    Len Nlkelayevldu Iolfluy
    Readmg shmdd be ax
    wlnvame asnossxble Wenfler
    an mm m our cram: Llhvary
    - Leo Tmsloy was a Russian rwovelwst
    I and moval phflmopher med lur
    A ms Ideas 01 nonviolenx reswslance m 5 We range 0, "and"
  
当然，你可以用 Pillow 库挑选图片进行清理，但是如果想把文字加工成普通人可以看懂的
效果，还需要花很多时间去处理。

下一节我们将介绍另一种方法来解决文字混乱的问题，尤其是当你愿意花一点儿时间训练
Tesseract 的时候。通过给 Tesseract 提供大量已知的文字与图片映射集，经过训练 Tesseract
就可以“学会”识别同一种字体，而且可以达到极高的精确率和准确率，甚至可以忽略图
片中文字的背景色和相对位置等问题。

> 11.3　读取验证码与训练Tesseract

虽然大多数人对单词“CAPTCHA”都很熟悉，但是很少人知道它的具体含义：全自动区
分计算机和人类的图灵测试（Completely Automated Public Turing test to tell Computers and
Humans Apart）。它的奇怪缩写似乎表示，它一直在扮演着十分奇怪的角色。其目的是为了
阻止网站访问，而不是让访问更通畅，它经常让人类和非人类的网络机器人深陷验证码识
别的泥潭不能自拔。

图灵测试首次出现在阿兰 ·图灵（Alan Turing）1950 年发表的论文“计算装置与智能”
（Computing Machinery and Intelligence）中。他在论文中描述了这样一种场景：一个人可以
和其他人交流，也可以通过计算机终端和人工智能程序交流。如果一番对话之后这个人不
能区分人和人工智能程序，那么就认为这个人工智能程序通过了图灵测试，图灵认为这个
人工智能程序就可以真正地“思考”所有的事情。

令人啼笑皆非的是，60 多年以后，我们开始用这些原本测试程序的题目来测试我们自己。
Google 的 reCAPTCHA 难得令人发指，作为目前最具有安全意识的流行网站，Google 拦截
了多达 25% 的准备访问网站的正常人类用户。 

大多数其他的验证码都是比较简单的。例如，流行的 PHP 内容管理系统 Drupal 有一个著
名的验证码模块（https://www.drupal.org/project/captcha），可以生成不同难度的验证码。

那么与其他验证码相比，究竟是什么让这个验证码更容易被人类和机器读懂呢？
* 字母没有相互叠加在一起，在水平方向上也没有彼此交叉。也就是说，可以在每一个字
母外面画一个方框，而不会重叠在一起。
* 图片没有背景色、线条或其他对 OCR 程序产生干扰的噪点。
* 虽然不能因一个图片下定论，但是这个验证码用的字体种类很少，而且用的是 sans-serif
字体（像“4”和“M”）和一种手写形式的字体（像“m”“C”和“3”）。
* 白色背景色与深色字母之间的对比度很高。
这个验证码只做了一点点改变，就让 OCR 程序很难识别。
* 字母和数据都使用了，这会增加待搜索字符的数量。
* 字母随机的倾斜程度会迷惑 OCR 软件，但是人类还是很容易识别的。
* 那个比较陌生的手写字体很有挑战性，在“C”和“3”里面还有额外的线条。另外这
个非常小的小写“m”，计算机需要进行额外的训练才能识别。

用下面的代码运行 Tesseract 识别图片：
  
    $tesseract captchaExample.png output
   
我们得到的结果 output.txt 是：
    
    4N\,,,C<3
   
虽然识别出了 4、C 和 3，但是显然这样的结果永远也不能识别出正确的验证码。

#### 训练Tesseract

要训练 Tesseract 识别一种文字，无论是晦涩难懂的字体还是验证码，你都需要向 Tesseract
提供每个字符不同形式的样本。

做这个枯燥的工作可能要花好几个小时的时间，你可能更想用这点儿时间找个好看的视频
或电影看看。首先要把大量的验证码样本下载到一个文件夹里。下载的样本数量由验证码
的复杂程度决定；我在训练集里一共放了 100 个样本（一共 500 个字符，平均每个字符 8
个样本；a~z 大小写字母加 0~9 数字，一共 62 个字符），应该足够训练的了。

提示：建议使用验证码的真实结果给每个样本文件命名（即 4MmC3.jpg）。这样可以帮你
一次性对大量的文件进行快速检查——你可以先把图片调成缩略图模式，然后通过文件名
对比不同的图片。这样在后面的步骤中进行训练效果的检查也会很方便。

第二步是准确地告诉 Tesseract 一张图片中的每个字符是什么，以及每个字符的具体位置。
这里需要创建一些矩形定位文件（box file），一个验证码图片生成一个矩形定位文件。一
个图片的矩形定位文件如下所示：
    
    4 15 26 33 55 0
    M 38 13 67 45 0
    m 79 15 101 26 0
    C 111 33 136 60 0
    3 147 17 176 45 0
   
第一列符号是图片中的每个字符，后面的 4 个数字分别是包围这个字符的最小矩形的坐标
（图片左下角是原点 (0,0)，4 个数字分别对应每个字符的左下角 x 坐标、左下角 y 坐标、右
上角 x 坐标和右上角 y 坐标），最后一个数字“0”表示图片样本的编号。

显然，手工创建这些图片矩形定位文件很无聊，不过有一些工具可以帮你完成。我很喜欢
在线工具 Tesseract OCR Chopper（http://pp19dd.com/tesseract-ocr-chopper/），因为它不需要
安装，也没有其他依赖，只要有浏览器就可以运行，而且用法很简单：上传图片，如果要
增加新矩形就单击“add”按钮，还可以根据需要调整矩形的尺寸，最后把新生成的矩形
定位文件复制到一个新文件里就可以了。

矩形定位文件必须保存在一个 .box 后缀的文本文件中。和图片文件一样，文本文件也是用
验证码的实际结果命名（例如，4MmC3.box）。另外，这样便于检查 .box 文件的内容和文
件的名称，而且按文件名对目录中的文件排序之后，就可以让 .box 文件与对应的图片文件
的实际结果进行对比。

你还需要创建大约 100 个 .box 文件来保证你有足够的训练数据。因为 Tesseract 会忽略那
些不能读取的文件，所以建议你尽量多做一些矩形定位文件，以保证训练足够充分。如果
你觉得训练的 OCR 结果没有达到你的目标，或者 Tesseract 识别某些字符时总是出错，多
创建一些训练数据然后重新训练将是一个不错的改进方法。

创建完满载 .box 文件和图片文件的数据文件夹之后，在做进一步分析之前最好备份一下这
个文件夹。虽然在数据上运行训练程序不太可能删除任何数据，但是创建 .box 文件用了你
好几个小时的时间，来之不易，稳妥一点儿总没错。此外，能够抓取一个满是编译数据的
混乱目录，然后再尝试一次，总是好的。

完成所有的数据分析工作和创建 Tesseract 所需的训练文件，一共有六个步骤。有一些工具
可以帮你处理图片和 .box 文件，不过目前 Tesseract 3.02 还不支持。
我写了一个 Python 版的解决方案（https://github.com/REMitchell/tesseract-trainer）来处理同
时包含图片文件和 .box 文件的数据文件夹，然后自动创建所有必需的训练文件。

这个解决方案的主要配置方式和步骤都在 main 方法（目前，作者已经在 GitHub 中将示例
代码修改为 __init__ 方法，符合 Python 的类定义原则）和 runAll 方法里：
    
    def main(self):
        languageName = "eng"
        fontName = "captchaFont"
        directory = "<path to images>"
        
    def runAll(self):
        self.createFontFile()
        self.cleanImages()
        self.renameFiles()
        self.extractUnicode()
        self.runShapeClustering()
        self.runMfTraining()
        self.runCnTraining()
        self.createTessData()
    
你需要动手设置的只有三个变量。
*  languageName

Tesseract 用三个字母的语言缩写代码表示识别的语言种类。可能大多数情况下，你都会
用“eng”表示英语（English）。

*  fontName

表示你选择的字体名称，可以是任意名称，但必须是一个不包含空格的单词。

*  directory

表示包含所有图片和 .box 文件的目录。建议你使用文件夹的绝对路径，但是如果你使
用相对路径，可能需要以 Python 代码运行的目录位置为原点。如果你使用绝对路径，
就可以在电脑的任意位置运行代码了。

让我们再看看 runAll 里每个函数的用法。

createFontFile 创建了一个 font_properties 文件，让 Tesseract 知道我们要创建的新字体：
 
    captchaFont 0 0 0 0 0
   
这个文件包括字体的名称，后面跟着若干 1 和 0，分别表示应该使用斜体、加粗或其他版
本的字体（用这些属性训练字体是一个很好玩儿的练习，不过超出了本书的介绍范围，感
兴趣的同学可以自己尝试）。

cleanImages 首先创建所有样本图片的高对比度版本，然后转换成灰度图，并进行一些清
理，让 Tesseract 更容易读取图片文件。如果你要处理的验证码图片上面有一些很容易过滤
掉的噪点，那么你可以在这里增加一些步骤来处理它们。

renameFiles 把 所 有 的 图 片 文 件 和 .box 文 件 的 文 件 名 改 变 成 Tesseract 需 要 的 形 式
（fileNumber 是文件序号，用来区别每个文件）：
    
    • <languageName>.<fontName>.exp<fileNumber>.box
    • <languageName>.<fontName>.exp<fileNumber>.tiff
  
extractUnicode 函数会检查所有已创建的 .box 文件，确定要训练的字符集范围。抽取出的
Unicode 会告诉你一共找到了多少个不重复的字符，这也是一个查询字符的好方法，如果
你漏了字符可以用这个结果快速排查。

之后的三个函数， runShapeClustering 、 runMfTraining 和 runCtTraining 分别用来创建
文件 shapetable 、 pfftable 和 normproto 。它们会生成每个字符的几何和形状信息，也为
Tesseract 提供计算字符若干可能结果的概率统计信息。

最后，Tesseract 会用之前设置的语言名称对数据文件夹编译出的每个文件进行重命名（例
如，shapetable 被重命名为 eng.shapetable），然后把所有的文件编译到最终的训练文件 eng.
traineddata 中。

你需要动手完成的唯一步骤，就是用下面的 Linux 和 Mac 命令行把刚刚创建的 eng.
traineddata 文件复制到 tessdata 文件夹里，Windows 系统类似：
   
    $cp /path/to/data/eng.traineddata $TESSDATA_PREFIX/tessdata
    
经过这些步骤之后，你就可以用这些 Tesseract 训练过的验证码来识别新图片了。现在我们
用 Tesseract 重新读取之前的示例验证码图片，就可以得到正确的结果了：
   
    $ tesseract captchaExample.png output;cat output.txt
    4MmC3
  
成功啦！相比之前的识别结果“4N\,,,C<3”，这个识别结果有明显的改善。

前面的内容只是对 Tesseract 库强大的字体训练和识别能力的一个简略概述。如果你对
Tesseract 的其他训练方法感兴趣，甚至打算建立自己的验证码训练文件库，或者想和全世
界的 Tesseract 爱好者分享自己对一种新字体的识别成果，那么我推荐你仔细阅读 Tesseract
的文档（https://github.com/tesseract-ocr/tesseract/wiki）。

> 11.4　获取验证码提交答案

许多流行的内容管理系统即使加了验证码模块，其众所周知的注册页面也经常会遭到网络
机器人的垃圾注册。比如在 http://pythonscraping.com/ 上，即使加了验证码（的确也很容易
识别）也不能让“汹涌澎湃”的垃圾注册有所缓解。

那么，这些网络机器人究竟是怎么做的呢？既然我们已经可以成功地识别出保存在电脑上
的验证码了，那么如何才能实现一个全能的网络机器人呢？下面的示例将综合前面几章的
内容来告诉你答案。如果你还没准备好，请至少浏览一下前几章关于表单提交和文件下载
的相关内容。

大多数网站生成的验证码图片都具有以下属性。

* 它们是服务器端的程序动态生成的图片。验证码图片的 src 属性可能和普通图片不太一
样，比如 \<img src="WebForm.aspx?id=8AP85CQKE9TJ"> ，但是可以和其他图片一样进行
下载和处理。
* 图片的答案存储在服务器端的数据库里。
* 很多验证码都有时间限制，如果你太长时间没解决就会失效。虽然这对网络机器人来说
不是什么问题，但是如果你想保留验证码的答案一会儿再使用，或者想通过一些方法延
长验证码的有效时限，可能很难成功。

常用的处理方法就是，首先把验证码图片下载到硬盘里，清理干净，然后用 Tesseract 处理
图片，最后返回符合网站要求的识别结果。
我在 http://pythonscraping.com/humans-only 创建了一个带验证码的评论表单，演示如何用
网络机器人破解验证码。程序如下所示：
    
    from urllib.request import urlretrieve
    from urllib.request import urlopen
    from bs4 import BeautifulSoup
    import subprocess
    import requests
    from PIL import Image
    from PIL import ImageOps

    def cleanImage(imagePath):
        image = Image.open(imagePath)
        image = image.point(lambda x: 0 if x<143 else 255)
        borderImage = ImageOps.expand(image, border=20, fill='white')
        borderImage.save(imagePath)

    html = urlopen("http://www.pythonscraping.com/humans-only")
    bsObj = BeautifulSoup(html)
    # 收集需要处理的表单数据（包括验证码和输入字段）
    imageLocation = bsObj.find("img", {"title": "Image CAPTCHA"})["src"]
    formBuildId = bsObj.find("input", {"name":"form_build_id"})["value"]
    captchaSid = bsObj.find("input", {"name":"captcha_sid"})["value"]
    captchaToken = bsObj.find("input", {"name":"captcha_token"})["value"]

    captchaUrl = "http://pythonscraping.com"+imageLocation
    urlretrieve(captchaUrl, "captcha.jpg")
    cleanImage("captcha.jpg")
    p = subprocess.Popen(["tesseract", "captcha.jpg", "captcha"],stdout=subprocess.PIPE,stderr=subprocess.PIPE)
    p.wait()
    f = open("captcha.txt", "r")

    # 清理识别结果中的空格和换行符
    captchaResponse = f.read().replace(" ", "").replace("\\n", "")
    print("Captcha solution attempt: "+captchaResponse)

    if len(captchaResponse) == 5:
        params = {"captcha_token":captchaToken, "captcha_sid":captchaSid,"form_id":"comment_node_page_form", "form_build_id": formBuildId,
            "captcha_response":captchaResponse, "name":"Ryan Mitchell",
            "subject": "I come to seek the Grail",
            "comment_body[und][0][value]":
            "...and I am definitely not a bot"}
        r = requests.post("http://www.pythonscraping.com/comment/reply/10",
                    data=params)
        responseObj = BeautifulSoup(r.text)
        if responseObj.find("div", {"class":"messages"}) is not None:
            print(responseObj.find("div", {"class":"messages"}).get_text())
    else:
        print("There was a problem reading the CAPTCHA correctly!")
   
值得注意的是，有两种异常情况会导致这个程序运行失败。第一种情况是，如果 Tesseract
从验证码图片中识别的结果不是五个字符（因为训练样本中验证码的所有有效答案都必须
是五个字符），结果不会被提交，程序失败。第二种情况是虽然识别的结果是五个字符，
被提交到了表单，但是服务器对结果不认可，程序仍然失败。在实际运行过程中，第一种
情况发生的可能性大约为 50%，发生时程序不会向表单提交，程序直接结束并提示验证码
识别错误。第二种异常情况发生的概率约为 20%，五个字符都对的概率约是 30%（每个字
母的识别正确率大约是 80%，5 个字符都识别正确的总概率是 32.8%）。

虽然这个程序的识别效果好像很差，但是用户尝试填写验证码的次数并没有限制，而且大
多数错误的识别结果都可以在提交到表单之前就被拦下来。因此，如果有一个识别结果提
交到表单并传送到服务器，那么验证码很可能就是正确的。如果这样解释并不能让你信
服，请记住这些都只是简单的猜测，准确率只有 0.0000001%。 3 程序只要运行三到四次就
可以识别出一个验证码，比简单的猜测 9 亿次还是要节省很多时间的！
    
           
           `,
        };
      }
    render() {
        return (
            <div >
                {/* 111111111111111
                <Py112 /> */}
                <MarkdownPreview value={this.state.value}/>
            </div>
        )
    }
}

export default Py25